package com.qd.panda.dc;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.JacksonUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.qd.common.panda.config.PandaParkConfigDTO;
import com.qd.panda.dc.dto.*;
import com.qd.panda.dc.dto.charge.ChargeActionReqDTO;
import com.qd.panda.dc.dto.charge.StartChargeRespDTO;
import com.qd.panda.dc.dto.charge.StatusChargeRespDTO;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

/**
 * 和数据中台的接口对接
 *
 * @author sjk
 */
@Component
@AllArgsConstructor
@Slf4j
public class DcService {
    private static final String SUBSCRIBE_URL = "/api/parkings/{0}/subscribe";
    private static final String CHARGE_ORDER = "/evcs/v1/orders";
    private static final String STATION_PAGE = "/evcs/v1/stations";
    private static final String QUERY_PAY_URL = "/api/parkings/{0}/pay";
    private static final String PAY_URL = "/api/parkings/{0}/pay";
    private static final String PARK_URL = "/api/parkings";
    private static final String CAR_IN_OUT_URL = "/api/parkings/{0}/car";
    private static final String PAY_RESULT_CALL_BACK_URL = "/api/parkings/{0}/pay/callback";
    private static final String ACTION_URL = "/api/parkings/{0}/action";
    private static final String STATUS_URL = "/evcs/v1/station/status";
    private final RestTemplate restTemplate;
    private final PandaParkConfigDTO pandaParkConfig;

    private static <T> T build(Supplier<ResponseEntity<T>> supplier) {
        try {
            final ResponseEntity<T> responseEntity = supplier.get();
            final HttpStatus statusCode = responseEntity.getStatusCode();
            if (statusCode == HttpStatus.OK) {
                return responseEntity.getBody();
            } else if (statusCode == HttpStatus.NOT_FOUND) {
                return null;
            } else {
                throw new ApiException("code: " + statusCode.value());
            }
        } catch (HttpClientErrorException e) {
            if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
                return null;
            }
            throw e;
        }
    }

    /**
     * 获得停车场信息
     *
     * @return 所有停车场信息
     */
    public ParkInfoDTO[] getParkInfo() {
        Map<String, String> signMap = buildSignMap(null);
        HttpHeaders httpHeaders = buildHeader(signMap);
        HttpEntity<Object> httpEntity = new HttpEntity<>(httpHeaders);
        return build(() -> restTemplate.exchange(pandaParkConfig.getDcDomain() + PARK_URL, HttpMethod.GET, httpEntity, ParkInfoDTO[].class));
    }

    /**
     * 查询停⻋费⽤
     *
     * @param parkId      停车场ID
     * @param plateNumber 车牌号
     * @param freeTime    免费停车时长
     * @return 费用信息
     */
    public PayInfoDTO queryPay(String parkId, String plateNumber, Long freeTime) {
        String url = pandaParkConfig.getDcDomain() + MessageFormat.format(QUERY_PAY_URL, parkId) + "?plateNumber={plateNumber}";
        Map<String, String> params = new HashMap<>(16);
        params.put("plateNumber", plateNumber);
        if (null != freeTime && freeTime > 0) {
            url += "&freeTime={freeTime}";
            params.put("freeTime", freeTime + "");
        }
        PayInfoDTO payInfoDTO = null;
        try {
            Map<String, String> signMap = buildSignMap(params);
            HttpHeaders httpHeaders = buildHeader(signMap);
            HttpEntity<Object> httpEntity = new HttpEntity<>(httpHeaders);
            String finalUrl = url;
            try {
                payInfoDTO = build(() -> restTemplate.exchange(finalUrl, HttpMethod.GET, httpEntity, PayInfoDTO.class, params));
                return payInfoDTO;
            } catch (Exception e) {
                log.error("查询停车费失败", e);
                throw new ApiException("查询停车费失败");
            }
        } finally {
            if (log.isDebugEnabled()) {
                log.debug("查询停车费用,url: {},parkId: {},plateNumber: {},freeTime: {},返回值: {}", url, parkId, plateNumber, freeTime, payInfoDTO);
            }
        }

    }

    /**
     * 查询进出场信息
     *
     * @param parkId      停车场ID，必填
     * @param plateNumber 车牌号，必填
     * @param startTime   开始时间，若不传，则查询最近⼀天⻋辆进出场信息
     * @param endTime     结束时间，若不传，则查询最近⼀天⻋辆进出场信息
     * @param isLast      是否查询最近时间⻋辆进出场数据
     * @param type        in进场 out出场
     * @return 进出场信息
     */
    public CarInOutDTO getCarInOut(String parkId, String plateNumber, String startTime, String endTime, Boolean isLast, String type) {
        String url = pandaParkConfig.getDcDomain() + MessageFormat.format(CAR_IN_OUT_URL, parkId) + "?plateNumber={plateNumber}";
        Map<String, String> params = new HashMap<>(16);
        params.put("plateNumber", plateNumber);
        if (StringUtils.hasLength(startTime)) {
            url += "&startTime={startTime}";
            params.put("startTime", startTime);
        }
        if (StringUtils.hasLength(endTime)) {
            url += "&endTime={endTime}";
            params.put("endTime", endTime);
        }
        if (null != isLast) {
            url += "&isLast={isLast}";
            params.put("isLast", isLast + "");
        }
        if (null != type) {
            url += "&type={type}";
            params.put("type", type);
        }
        CarInOutDTO carInOutDTO = null;
        try {
            Map<String, String> signMap = buildSignMap(params);
            HttpHeaders httpHeaders = buildHeader(signMap);
            HttpEntity<Object> httpEntity = new HttpEntity<>(httpHeaders);
            String finalUrl = url;
            carInOutDTO = build(() -> restTemplate.exchange(finalUrl, HttpMethod.GET, httpEntity, CarInOutDTO.class, params));
            return carInOutDTO;
        } finally {
            if (log.isDebugEnabled()) {
                log.debug("查询进出场信息,url: {},parkId: {},plateNumber: {},startTime: {},endTime: {},isLast: {},type: {},返回值: {}", url, parkId, plateNumber, startTime, endTime, isLast, type, carInOutDTO);
            }
        }

    }

    /**
     * ⽀付停⻋费⽤
     *
     * @param parkId         停车场ID
     * @param plateNumber    车牌号
     * @param amount         支付金额
     * @param payNo          停车订单号
     * @param startChargeSeq 充电订单号
     * @param parkTime       停车时长
     * @param laneCode       车道号
     * @param inTime         进场时间
     * @param outTime        出场时间
     * @return http状态码200就是成功
     */
    @SneakyThrows
    public boolean pay(String parkId, String plateNumber, BigDecimal amount, String payNo, String startChargeSeq, String parkTime, String laneCode, String inTime, String outTime) {
        String url = pandaParkConfig.getDcDomain() + MessageFormat.format(PAY_URL, parkId);
        // 组装body参数
        PayReqDTO payReqDTO = new PayReqDTO();
        payReqDTO.setPlateNumber(plateNumber);
        payReqDTO.setAmount(amount);
        if (StringUtils.hasLength(payNo)) {
            payReqDTO.setPayNo(payNo);
        }
        if (StringUtils.hasLength(startChargeSeq)) {
            payReqDTO.setStartChargeSeq(startChargeSeq);
        }
        if (StringUtils.hasLength(parkTime)) {
            payReqDTO.setParkTime(parkTime);
        }
        if (StringUtils.hasLength(laneCode)) {
            payReqDTO.setLaneCode(laneCode);
        }
        if (StringUtils.hasLength(inTime)) {
            payReqDTO.setInTime(inTime);
        }
        if (StringUtils.hasLength(outTime)) {
            payReqDTO.setOutTime(outTime);
        }
        int httpCode = -9999;
        boolean b = false;
        try {
            Map<String, String> signMap = buildSignContent(JacksonUtil.getObjectMapper().writeValueAsString(payReqDTO));
            HttpHeaders httpHeaders = buildHeader(signMap);
            HttpEntity<Object> httpEntity = new HttpEntity<>(payReqDTO, httpHeaders);
            try {
                final ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
                HttpStatus statusCode = responseEntity.getStatusCode();
                httpCode = statusCode.value();
                b = (httpCode == HttpStatus.OK.value());
            } catch (Exception e) {
                log.error("⽀付停⻋费⽤{}", e.getMessage());
            }
            return b;
        } finally {
            if (log.isDebugEnabled()) {
                log.debug("ETC支付停车费用,url: {},parkId: {},plateNumber: {},amount: {},payNo: {},startChargeSeq: {},parkTime: {},laneCode: {},inTime: {},outTime: {},返回值status: {},是否支付成功: {}", url, parkId, plateNumber, amount, payNo, startChargeSeq, parkTime, laneCode, inTime, outTime, httpCode, b);
            }
        }

    }

    /**
     * 订阅车辆信息，该车辆在该停车场的事件会推送过来
     *
     * @param parkId      停车场ID
     * @param plateNumber 车牌号
     * @param cancel      是否取消订阅
     */
    @SneakyThrows
    public void subscribe(String parkId, String plateNumber, boolean cancel) {
        String url = pandaParkConfig.getDcDomain() + MessageFormat.format(SUBSCRIBE_URL, parkId);
        SubscribeReqDTO subscribeReqDTO = new SubscribeReqDTO().setPlateNumber(plateNumber).setCancel(cancel);
        int httpCode = -9999;
        try {
            Map<String, String> signMap = buildSignContent(JacksonUtil.getObjectMapper().writeValueAsString(subscribeReqDTO));
            HttpHeaders httpHeaders = buildHeader(signMap);
            HttpEntity<Object> httpEntity = new HttpEntity<>(subscribeReqDTO, httpHeaders);
            final ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
            HttpStatus statusCode = responseEntity.getStatusCode();
            httpCode = statusCode.value();
            if (httpCode != (HttpStatus.OK.value())) {
                throw new ApiException(responseEntity.getBody());
            }
        } finally {
            if (log.isDebugEnabled()) {
                log.debug("订阅车辆信息,url: {},parkId: {},plateNumber: {},cancel: {},返回值status: {}", url, parkId, plateNumber, cancel, httpCode);
            }
        }

    }

    /**
     * 查询充电订单(数据中台建议使用statusCharge，当前接口作废)
     *
     * @param startChargeSeq 订单号
     * @return 订单信息
     */
    @Deprecated
    public DcOrderDTO getChargeOrder(String startChargeSeq) {
        String url = pandaParkConfig.getDcDomain() + CHARGE_ORDER + "?startChargeSeq={startChargeSeq}";
        Map<String, String> params = new HashMap<>(16);
        params.put("startChargeSeq", startChargeSeq);
        DcOrderDTO dcOrderDTO = null;
        try {
            Map<String, String> signMap = buildSignMap(params);
            HttpHeaders httpHeaders = buildHeader(signMap);
            HttpEntity<Object> httpEntity = new HttpEntity<>(httpHeaders);
            dcOrderDTO = build(() -> restTemplate.exchange(url, HttpMethod.GET, httpEntity, DcOrderDTO.class, params));
            return dcOrderDTO;
        } finally {
            if (log.isDebugEnabled()) {
                log.debug("查询充电订单,url: {},startChargeSeq: {},返回值status: {}", url, startChargeSeq, dcOrderDTO);
            }
        }

    }

    /**
     * 支付回执，只用于支付了停车费以后回执
     *
     * @param parkId  停车场ID
     * @param payNo   停车订单号
     * @param etcPay  是否ETC支付
     * @param success 是否支付成功
     */
    @SneakyThrows
    public void payResultCallBack(String parkId, String payNo, boolean etcPay, boolean success) {
        String url = pandaParkConfig.getDcDomain() + MessageFormat.format(PAY_RESULT_CALL_BACK_URL, parkId);
        PayResultCallBackReqDTO req = new PayResultCallBackReqDTO().setPayNo(payNo).setEtcPay(etcPay).setSuccess(success);
        int httpCode = -9999;
        try {
            Map<String, String> signMap = buildSignContent(JacksonUtil.getObjectMapper().writeValueAsString(req));
            HttpHeaders httpHeaders = buildHeader(signMap);
            HttpEntity<Object> httpEntity = new HttpEntity<>(req, httpHeaders);
            final ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
            HttpStatus statusCode = responseEntity.getStatusCode();
            httpCode = statusCode.value();
            if (httpCode != (HttpStatus.OK.value())) {
                throw new ApiException(responseEntity.getBody());
            }
        } finally {
            log.debug("支付回执,url: {},parkId: {},payNo: {},etcPay:{},success: {},返回值status: {}", url, parkId, payNo, etcPay, success, httpCode);
        }

    }

    /**
     * 查询充电站信息
     *
     * @param pageNo   页数
     * @param pageSize 每页显示条数
     * @return 充电站列表信息
     */
    @SneakyThrows
    public DcPage<ChargeStationDTO> getChargeStationPage(Integer pageNo, Integer pageSize) {
        if (null == pageNo) {
            pageNo = 1;
        }
        if (null == pageSize) {
            pageSize = 10;
        }
        String url = pandaParkConfig.getDcDomain() + STATION_PAGE + "?pageNo={pageNo}&pageSize={pageSize}";
        Map<String, String> params = new HashMap<>(16);
        params.put("pageNo", String.valueOf(pageNo));
        params.put("pageSize", String.valueOf(pageSize));
        String responseMsg = null;
        try {
            Map<String, String> signMap = buildSignMap(params);
            HttpHeaders httpHeaders = buildHeader(signMap);
            HttpEntity<Object> httpEntity = new HttpEntity<>(httpHeaders);
            responseMsg = build(() -> restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class, params));
            if (StringUtils.hasLength(responseMsg)) {
                return JacksonUtil.getObjectMapper().readValue(responseMsg, new TypeReference<>() {
                });
            }
            return new DcPage<>();
        } finally {
            log.info("查询充电站信息,url: {},pageNo: {},pageSize: {},返回值: {}", url, pageNo, pageSize, responseMsg);
        }

    }

    /**
     * 根据车牌查询插抢状态
     *
     * @param plateNumber 车牌号
     * @return 状态
     */
    @SneakyThrows
    public ChargeStatusDTO status(String plateNumber) {
        String url = pandaParkConfig.getDcDomain() + STATUS_URL + "?plateNumber={plateNumber}";
        Map<String, String> params = new HashMap<>(1);
        params.put("plateNumber", plateNumber);
        String responseMsg = null;
        try {
            Map<String, String> signMap = buildSignMap(params);
            HttpHeaders httpHeaders = buildHeader(signMap);
            HttpEntity<Object> httpEntity = new HttpEntity<>(httpHeaders);
            responseMsg = build(() -> restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class, params));
            if (StringUtils.hasText(responseMsg) && responseMsg.length() > 2) {
                return JacksonUtil.getObjectMapper().readValue(responseMsg, ChargeStatusDTO.class);
            }
            return null;
        } finally {
            log.info("根据车牌查询插抢状态,url: {},plateNumber: {},返回值: {}", url, plateNumber, responseMsg);
        }

    }

    /**
     * 停止充电
     *
     * @param parkId 停车场ID
     * @param req    充电信息
     */
    public void stopCharge(String parkId, ChargeActionReqDTO req) {
        //返回200就表示停止成功
        chargeAction(parkId, req);
    }

    /**
     * 开始充电
     *
     * @param parkId 停车场ID
     * @param req    充电信息
     * @return 消息
     */
    @SneakyThrows
    public StartChargeRespDTO startCharge(String parkId, ChargeActionReqDTO req) {
        final ResponseEntity<String> responseEntity = chargeAction(parkId, req);
        final String responseMsg = responseEntity.getBody();
        return JacksonUtil.getObjectMapper().readValue(responseMsg, StartChargeRespDTO.class);
    }

    /**
     * 查询充电状态
     *
     * @param parkId 停车场ID
     * @param req    充电信息
     * @return 消息
     */
    @SneakyThrows
    public StatusChargeRespDTO statusCharge(String parkId, ChargeActionReqDTO req) {
        final ResponseEntity<String> responseEntity = chargeAction(parkId, req);
        final String responseMsg = responseEntity.getBody();
        return JacksonUtil.getObjectMapper().readValue(responseMsg, StatusChargeRespDTO.class);
    }

    /**
     * 充电动作
     *
     * @param parkId 停车场ID
     * @param req    充电信息
     * @return 开始充电、结束充电、充电状态查询
     */
    @SneakyThrows
    public ResponseEntity<String> chargeAction(String parkId, ChargeActionReqDTO req) {
        String url = pandaParkConfig.getDcDomain() + MessageFormat.format(ACTION_URL, parkId);
        int httpCode = -9999;
        String responseMsg = null;
        try {
            Map<String, String> signMap = buildSignContent(JacksonUtil.getObjectMapper().writeValueAsString(req));
            HttpHeaders httpHeaders = buildHeader(signMap);
            HttpEntity<Object> httpEntity = new HttpEntity<>(req, httpHeaders);
            final ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
            httpCode = responseEntity.getStatusCode().value();
            if (httpCode == (HttpStatus.OK.value())) {
                responseMsg = responseEntity.getBody();
            } else {
                throw new ApiException(responseEntity.getBody());
            }
            return responseEntity;
        } finally {
            log.debug("充电动作,url: {},parkId: {},req: {},httpCode: {},返回值: {}", url, parkId, req, httpCode, responseMsg);
        }

    }

    private Map<String, String> buildSignMap(Map<String, String> map) {
        Map<String, String> params = getBaseMap();
        if (null != map) {
            params.putAll(map);
        }
        sign(params);
        return params;
    }

    @SneakyThrows
    private Map<String, String> buildSignContent(String bizContent) {
        Map<String, String> params = getBaseMap();
//        params.put("page", "1");
//        params.put("name","中文");
        if (StringUtils.hasLength(bizContent)) {
            params.put("biz_content", bizContent);
        }
//        log.debug("params: {}", objectMapper.writeValueAsString(params));
        sign(params);
        if (StringUtils.hasLength(bizContent)) {
            params.remove("biz_content");
        }
        return params;
    }

    private void sign(Map<String, String> params) {
        final String signContent = SignerCommon.getSignContent(params);
//        log.debug("signContent: {}", signContent);
        String sign = SignerCommon.sign(signContent, SignerCommon.PRIVATE_KEY);
//        log.debug("signContent: {},key: {}", signContent, pandaParkConfig.getDcSecurity());
//        log.debug("sign: {}", sign);
        params.put("sign", sign);
    }

    private Map<String, String> getBaseMap() {
        String nonce = IdWorker.getIdStr();
        String timestamp = DateUtil.now();
        Map<String, String> params = new HashMap<>(6);
        params.put("timestamp", timestamp);
        params.put("nonce", nonce);
        params.put("app_id", pandaParkConfig.getDcAppId());
        return params;
    }

    private HttpHeaders buildHeader(Map<String, String> signMap) {
        StringBuilder authorization = new StringBuilder();
        for (String key : signMap.keySet()) {
            authorization.append(key);
            authorization.append("=");
            authorization.append(signMap.get(key));
            authorization.append(",");
        }
        HttpHeaders httpHeaders = new HttpHeaders();
        final String str = authorization.substring(0, authorization.length() - 1);
//        log.debug("Authorization: {}", str);
        httpHeaders.add("Authorization", str);
        httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString());
        return httpHeaders;
    }
}
